using System;
using System.Collections.Generic;
using CLELCore;
using CLELCore.Assembly;
using CLELCore.Assembly.Descriptors;
using CLELCore.Assembly.Descriptors.ClassDescriptors;
using CLELCore.Assembly.MethodHeaderDataSections;
using CLELCore.Assembly.CLELInstructions;
using opt.Analysis.Graph;
using opt.Analysis.Graph.CallGraph;
using opt.Analysis.Graph.CallGraph.CallGraphVisitor;
using opt.Tools.Methods;

namespace opt.lib.ClassIFE
{
	public class ClassIFE : IOptimization
	{
		private const String CONFIG_PATH = "lib/ClassIFE.xml";
		private const double DEFAULT_PERCENTAGE_CODE_GROWTH = 0.1;
		private CLEL _clel;
		private CLELLogger _log;
		private int _code_growth;
		private int _entry_point_token;

		//private int count;

		public String Name
		{
			get
			{
				return "Class Inline Function Expansion";
			}
		}

		public void DoOptimization(CLEL clel,CLELLogger log)
		{
			_clel = clel;
			_log = log;
			_entry_point_token = _clel.GetEntryPointToken().GetEncodedToken();

			_log.WriteLine("ClassIFE: " + _clel.Path + "\n");
			LoadConfig();
			ProcessClasses();
		}

		private void LoadConfig()
		{
			OptimizationConfiguration config = new OptimizationConfiguration(CONFIG_PATH);
			String xml_code_growth = config.GetConfigValue("code_growth");
			xml_code_growth = xml_code_growth.Trim();
			int total_code_size = _clel.GetCodeSize();
			_code_growth = (int)(total_code_size * DEFAULT_PERCENTAGE_CODE_GROWTH);
			if((xml_code_growth.Substring(xml_code_growth.Length-1,1)) == "%")
			{
				//strip off percentage character
				xml_code_growth = xml_code_growth.Substring(0,xml_code_growth.Length-1);
				try
				{
					_code_growth = (int)(total_code_size * Convert.ToInt32(xml_code_growth)/100);
				}
				catch(Exception)
				{
					//ignore exception and use default value
				}
			}
			else
			{
				try
				{
					_code_growth = Convert.ToInt32(xml_code_growth);
				}
				catch(Exception)
				{
					//ignore exception and use default value
				}
				
			}
		}
		
		private void ProcessClasses()
		{
			List<ClassDescriptor> class_descriptors = _clel.GetInternalClasses();
			List<MethodDescriptor> method_descriptors;
			int i,class_count = class_descriptors.Count;
			CallGraph<ClassIFEArcInfo,ClassIFENodeInfo> cg;
	
			//count = 0;
			TypeDefClassDescriptor tycd;
			for(i = 0;i < class_count;i++)
			{
				if(class_descriptors[i].Type == ClassDescriptor.TYPEDEF_CLASSDESCRIPTOR)
				{
					//if(count > 18)
					//	return;
					tycd = (TypeDefClassDescriptor)class_descriptors[i];
					method_descriptors = tycd.Methods;
					//for(int k = 0;k < method_descriptors.Count;k++)
					//	Console.WriteLine(method_descriptors[k].MethodToken);
					//Console.WriteLine("\n\n");
					cg = new CallGraph<ClassIFEArcInfo,ClassIFENodeInfo>(method_descriptors,_clel);
					InlineClass(cg);
				}
			}
		}

		private void InlineClass(CallGraph<ClassIFEArcInfo,ClassIFENodeInfo> cg)
		{
			CallGraphVisitor<ClassIFEArcInfo,ClassIFENodeInfo> visitor = new CallGraphVisitor<ClassIFEArcInfo,ClassIFENodeInfo>(cg);
			ClassCallGraphVisitor ccgv;
			int arc_index;
			GraphNode<ClassIFEArcInfo,ClassIFENodeInfo> caller;
			List<CLELInstruction> caller_code;
			MethodDescriptor caller_md;
			GraphNode<ClassIFEArcInfo,ClassIFENodeInfo> callee;
			List<CLELInstruction> callee_code;
			MethodDescriptor callee_md;
			int code_size;
			int num_inst;
			int call_index;
			int i;
			MethodHeaderDataSection caller_seh;
			MethodHeaderDataSection callee_seh;
			String callee_name;
			do
			{
				ccgv = new ClassCallGraphVisitor();
				visitor.Accept(ccgv);
				caller = ccgv.Caller;
				arc_index = ccgv.ArcIndex;
				if(caller != null)
				{
					caller_code = caller.Data.Code;
					caller_md = caller.Data.MethodDescriptor;
					callee = caller[arc_index].Next;
					callee_code = callee.Data.Code;
					callee_md = callee.Data.MethodDescriptor;

					caller_seh = caller_md.SEHDataSection;
					callee_seh = callee_md.SEHDataSection;
					
					callee_name = _clel.GetMethodsName(callee_md.MethodToken);
					callee_name = callee_name.Substring(0,callee_name.Length-1);
					//do not inline if either has any seh
					//do not inline constructors or copy constructors
					//do not inline anything into Main
					if(caller_seh == null && callee_seh == null &&
						callee_name != ".ctor" && callee_name != ".cctor" && 
						caller_md.MethodToken.GetEncodedToken() != _entry_point_token)
					{
						//if(count > 18)
						//	return;
						code_size = CLELInstructionTools.CodeSize(caller_code);
						//if method to inline is more than what is left, break out
						if(code_size > _code_growth)
							break;
						Console.WriteLine(_clel.GetMethodsName(caller_md.MethodToken));
						Console.WriteLine(_clel.GetMethodsName(callee_md.MethodToken)+"\n\n");
						call_index = caller[arc_index].Data.CallIndex;
						num_inst = caller_code.Count;
						//inline callee into caller
						InlineMethod.Inline(_clel,caller_code,caller_md,
							call_index,
							callee_code,callee_md);
						//count++;
						num_inst = caller_code.Count - num_inst;
						for(i = 0;i < caller.Count;i++)
						{
							if(!caller[i].Data.Deleted)
							{
								if(caller[i].Data.CallIndex > call_index)
								{
									caller[i].Data.CallIndex += num_inst;
								}
							}
						}
						//subtract code size difference from _code_growth
						_code_growth -= (CLELInstructionTools.CodeSize(caller_code) - code_size);
						//delete arc
						caller[arc_index].Data.Deleted = true;
					}
					else
					{
						//delete arc
						caller[arc_index].Data.Deleted = true;
					}
				}
			}
			while(_code_growth > 0 && caller != null);
		}
	}
}
